package com.rockbb.common.simpleframe.action;

import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.log4j.Logger;

import com.rockbb.common.simpleframe.config.SimpleConfig;
import com.rockbb.common.simpleframe.result.ActionResult;
import com.rockbb.common.utilities.i18n.Language;
import com.rockbb.common.utilities.text.StringUtils;

public class BasicAction implements ActionSupport
{
	private static Logger logger = Logger.getLogger(BasicAction.class);
	private ActionContext context;
	private HttpServletRequest request;
	private HttpServletResponse response;
	private Map<String, Object> root;

	public void initialize(ActionContext context, HttpServletRequest request, HttpServletResponse response,
			Map<String, Object> root)
	{
		this.context = context;
		this.request = request;
		this.response = response;
		this.root = root;
		try
		{
			request.setCharacterEncoding(SimpleConfig.getConfig("encoding"));
			response.setCharacterEncoding(SimpleConfig.getConfig("encoding"));
		}
		catch (Exception e)
		{
		}
	}

	public void prepare()
	{
		root.put(ActionResult.MAP_KEY_CONFIGS, SimpleConfig.getConfigs());
	}

	public String intercepters()
	{
		return SUCCESS;
	}

	/**
	 * set the JSON object for output with callback method
	 * 
	 * @param obj
	 * @param callback	- can be null
	 */
	protected void set_json_obj(Object obj, String callback)
	{
		getRoot().put(ActionResult.MAP_KEY_JSON, obj);
		if (callback != null && callback.length() > 0)
			getRoot().put(ActionResult.MAP_KEY_JSON_CALLBACK, callback);
	}

	/**
	 * 用于raw结果类型中绑定输出字符串
	 * 
	 * @param string
	 */
	protected void set_raw_string(String string)
	{
		getRoot().put(ActionResult.MAP_KEY_RAW, string);
	}

	/**
	 * 为结果类型5:Stream 提供的数据填充接口, 分别为
	 * 
	 * @param streamtype 数据流类型, 如 image/jpeg
	 * @param disposition 数据输出的处理方式, 如 inline, attachment
	 * @param filename 文件名
	 * @param streambytes 数据流byte[]
	 */
	protected void set_streambytes(String streamtype, String disposition, String filename, byte[] streambytes)
	{
		getRoot().put(ActionResult.MAP_KEY_STREAM_TYPE, streamtype);
		getRoot().put(ActionResult.MAP_KEY_STREAM_DISPOSITION, disposition);
		getRoot().put(ActionResult.MAP_KEY_STREAM_FILENAME, filename);
		getRoot().put(ActionResult.MAP_KEY_STREAM, streambytes);
	}

	/**
	 * 用于redirect结果类型中绑定跳转目标URL
	 * 
	 * @param url
	 */
	protected String redirect(String url)
	{
		getRoot().put(ActionResult.MAP_KEY_REDIRECT, url);
		return REDIRECT;
	}

	/**
	 * 在action中获取context
	 * 
	 * @return
	 */
	protected ActionContext getContext()
	{
		return context;
	}

	/**
	 * 在action中获取request
	 * 
	 * @return HttpServletRequest
	 */
	protected HttpServletRequest getRequest()
	{
		return request;
	}

	/**
	 * 在action中获取response
	 * 
	 * @return HttpServletResponse
	 */
	protected HttpServletResponse getResponse()
	{
		return response;
	}

	/**
	 * 得到完整session数据
	 * 
	 * @return session data map root
	 */
	protected Map<String, Object> getRoot()
	{
		return root;
	}

	/**
	 * 获取语系集合
	 * 
	 * @return
	 */
	protected List<Language> getLanguages()
	{
		return SimpleConfig.getLanguages();
	}

	/**
	 * 获取当前Action的链接地址
	 * 
	 * @return
	 */
	protected String baseLink()
	{
		return SimpleConfig.getConfig("root_path") + "/" + context.getActionName().replaceAll("\\.", "/")
				+ SimpleConfig.getConfig("extension");
	}

	/**
	 * 获取Action的链接地址, 可以增加参数, 并且使用绝对路径
	 * 
	 * @param params 如果有参数的话. 使用 [key][value], [key][value], ... 的形式, 可以为null
	 * @param isAbsolute 是否需要使用带域名的绝对地址
	 * @return
	 */
	protected String baseLink(List<String[]> params, boolean isAbsolute)
	{
		String url = SimpleConfig.getConfig("root_path");
		if (isAbsolute)
		{
			url = SimpleConfig.getConfig("site_base") + url;
		}
		url += "/" + context.getActionName().replaceAll("\\.", "/") + SimpleConfig.getConfig("extension");

		return StringUtils.appendParametersToURL(url, params, SimpleConfig.getConfig("encoding"));
	}

	/**
	 * 根据Action Name, 获取Action的链接地址
	 * 
	 * @param act_name
	 * @return
	 */
	public static String baseLink(String act_name)
	{
		return baseLink(act_name, null, false);
	}

	/**
	 * 根据Action Name, 获取Action的链接地址
	 * 
	 * @param act_name action name - 中间如果有 '.' 号的话, 会被转换成 '/' 
	 * @param params 如果有参数的话. 使用 [key][value], [key][value], ... 的形式, 可以为null
	 * @param isAbsolute 是否需要使用带域名的绝对地址
	 * @return
	 */
	public static String baseLink(String act_name, List<String[]> params, boolean isAbsolute)
	{
		String url = SimpleConfig.getConfig("root_path");

		if (isAbsolute)
		{
			url = SimpleConfig.getConfig("site_base") + url;
		}

		if (SimpleConfig.getActionContext(act_name) != null)
		{
			url += "/" + SimpleConfig.getActionContext(act_name).getActionName().replaceAll("\\.", "/") 
				+ SimpleConfig.getConfig("extension");
		}

		return StringUtils.appendParametersToURL(url, params, SimpleConfig.getConfig("encoding"));
	}

	public static String verifyRedirect(HttpServletRequest request, String redirect) 
	{
		if (redirect == null || redirect.equals("")) 
		{
			redirect = request.getHeader("Referer");
		}

		// Check if it is an internal link
		boolean isRecursive = false;
		String host = request.getHeader("Host");
		if (redirect != null && ( host != null || redirect.indexOf(host) > -1)) 
		{
			if (redirect.indexOf(baseLink("userlogin")) > -1)
				isRecursive = true;
		}

		// If the redirect link is empty or a user panel page, set it to the user home
		if (redirect == null || redirect.length() == 0 || isRecursive) 
		{
			redirect = baseLink("index");
		}

		return redirect;
	}

	public class DefaultRequestBean extends RequestBean
	{
		@Override
		protected void loadRequest(){}

		@Override
		protected boolean validate()
		{
			return true;
		}
	}

	protected abstract class FileRequestBean extends RequestBean
	{
		// Set the threshold to 2MB, files smaller than it will be stored in memory
		public static final int SIZE_THRESHOLD = 2048000;
		// Set the Max file length to 32MB
		public static final long SIZE_MAX = 48 * 1024000L;

		private Map<String, String[]> parameters;
		private Map<String, MultipartFile> multipartFiles;

		@Override
		protected void initialise()
		{
			parameters = new HashMap<String, String[]>();
			multipartFiles = new HashMap<String, MultipartFile>();

			DiskFileItemFactory factory = new DiskFileItemFactory();
			factory.setSizeThreshold(SIZE_THRESHOLD);
			ServletFileUpload uploader = new ServletFileUpload(factory);
			uploader.setFileSizeMax(SIZE_MAX);
			uploader.setHeaderEncoding(SimpleConfig.getConfig("encoding"));

			// Pre-populate parameters Map with query parameters.
			Enumeration<String> paramNames = getRequest().getParameterNames();
			while (paramNames.hasMoreElements()) {
				String paramName = paramNames.nextElement();
				String[] values = getRequest().getParameterValues(paramName);
				if (values != null)
					parameters.put(paramName, values);
			}

			if(ServletFileUpload.isMultipartContent(getRequest()))
			{
				try
				{
					// Extract files and parameters.
					List<FileItem> fileItems = uploader.parseRequest(getRequest());
					for (Iterator<FileItem> it = fileItems.iterator(); it.hasNext();) 
					{
						FileItem fileItem = it.next();
						if (fileItem.isFormField()) // If it is not a file
						{
							String value = null;
							try 
							{
								value = fileItem.getString(SimpleConfig.getConfig("encoding"));
							}
							catch (UnsupportedEncodingException ex) 
							{
								logger.info("Could not decode multipart item '" + fileItem.getFieldName() 
										+"' with encoding '" + SimpleConfig.getConfig("encoding") 
										+ "': using platform default");
								value = fileItem.getString();
							}

							String[] curParam = (String[]) parameters.get(fileItem.getFieldName());
							if (curParam == null) 
							{
								// simple form field
								parameters.put(fileItem.getFieldName(), new String[] { value });
							}
							else 
							{
								// array of simple form fields
								String[] newParam = StringUtils.addStringToArray(curParam, value);
								parameters.put(fileItem.getFieldName(), newParam);
							}
						}
						else 
						{
							// multipart file field
							CommonsMultipartFile file = new CommonsMultipartFile(fileItem);
							multipartFiles.put(file.getName(), file);
							if (logger.isDebugEnabled())
							{
								logger.debug("Found multipart file [" + file.getName() + "] of size " 
									+ file.getSize() + " bytes with original filename [" 
									+ file.getOriginalFilename() + "], stored " 
									+ file.getStorageDescription());
							}
						}
					}
				}
				catch (FileUploadBase.SizeLimitExceededException ex) 
				{
					logger.info("SizeLimitExceededException: Could not parse multipart request");
					logger.debug(ex.getMessage(), ex);
				}
				catch(FileUploadException ex) 
				{
					logger.info("FileUploadException: Could not parse multipart request");
					logger.debug(ex.getMessage(), ex);
				}
			}
		}

		/**
		 * Return an Iterator of String objects containing the parameter names of the
		 * multipart files contained in this request. These are the field names of
		 * the form (like with normal parameters), not the original file names.
		 * @return the names of the files
		 */
		public Iterator<String> getFileNames()
		{
			return this.multipartFiles.keySet().iterator();
		}

		/**
		 * Return the contents plus description of an uploaded file in this request,
		 * or null if it does not exist.
		 * @param name a String specifying the parameter name of the multipart file
		 * @return the uploaded content in the form of a MultipartFile object
		 */
		public MultipartFile getFile(String name)
		{
			return this.multipartFiles.get(name);
		}

		/**
		 * Return a Map of the multipart files contained in this request.
		 * @return a map containing the parameter names as keys, and the
		 * MultipartFile objects as values
		 * @see MultipartFile
		 */
		public Map<String, MultipartFile> getFileMap()
		{
			return this.multipartFiles;
		}

		@Override
		protected String[] getParameterValues(String key)
		{
			return parameters.get(key);
		}

		@Override
		protected String getParameter(String key)
		{
			String[] values = parameters.get(key);
			if (values != null && values.length > 0)
				return values[0];
			else
				return null;
		}
	}
	
	/**
	 * INPUT类型action的请求数据基类, 需要实现请求数据的装载, 验证方法
	 * 
	 * 预置变量说明:
	 * 
	 * token: 用于验证表单有效性的字段
	 * redirect: 表单提交成功后的跳转URL
	 * error_msg: 放置错误字段信息以及其他信息的string map
	 * 
	 * @author Administrator
	 *
	 */
	protected abstract class RequestBean extends BasicRequestBean
	{
		private static final int SUBMITTED = 1;

		private int submit;
		private String token;
		private String redirect;
		private Map<String, String> error_msg;

		public String getToken(){return token;}
		public void setToken(String token){this.token = token;}

		public String getRedirect(){return redirect;}
		public void setRedirect(String redirect){this.redirect = redirect;}

		public boolean isSubmitted() {return (submit == SUBMITTED)? true:false;}

		public Map<String, String> getError_msg(){return error_msg;}

		public void clear_error_msg(){error_msg.clear();}
		public void put_error_msg(String key, String msg){error_msg.put(key, msg);}
		public void remove_error_msg(String key){error_msg.remove(key);}

		public RequestBean()
		{
			initialise();
			this.error_msg = new HashMap<String, String>();
			this.error_msg.put(" ", " ");
			this.redirect = verifyRedirect(getRequest(), get("redirect"));
			this.submit = getInt("form_submit");
			this.token = get("token");
			loadRequest();
		}

		protected void initialise() {}
		protected abstract void loadRequest();
		protected abstract boolean validate();
	}

	public class BasicRequestBean extends GenericRequestBean
	{

		@Override
		protected String[] getParameterValues(String key)
		{
			return getRequest().getParameterValues(key);
		}

		@Override
		protected String getParameter(String key)
		{
			return getRequest().getParameter(key);
		}

		@Override
		public HttpServletRequest getRequest()
		{
			return request;
		}

		@Override
		public HttpServletResponse getResponse()
		{
			return response;
		}
	}
}
